import Ws           from "ws";
import Express      from "express";
import cookieParser from "cookie-parser";
import bodyParser   from "body-parser";

const GlobalConfig = require("../config/global.config.json");
const ManagerConfig = require("../config/manager.config.json");
const ServerConfig = require("../config/server.config.json");
const PacketAPI = require("../config/packet.api.json");

import GamePlugin   from "./plugin/gamePlugin";
import ConnectionPlugin from "./plugin/connectionPlugin";

import UserManager   from "./manager/UserManager";
import PacketManager from "./manager/PacketManager";
import RegionPlugin  from "./plugin/regionPlugin";

import PomeloLogger  from "pomelo-logger";
const Logger = PomeloLogger.getLogger("GameServer", process.pid, __filename);

class Server extends GamePlugin {
    constructor(serverId, port, host) {
        super("Server:" + serverId);

        this.serverId         = serverId;

        // All Server Plugins
        this.regionPlugin     = null;
        this.connectionPlugin = null;

        // Todo: We Can Set Config In A "plugin.config.json" File, To Auto Load Server Plugins
        // this.plugins          = [];
        // this._plugins         = {};

        // Web Socket Server Instance, Wait To Handle Client Logic
        this._host = host || "127.0.0.1";
        this._port = port || 15000;
        this.socketServer  = null;

        // Http Server Instance To Response The Status Of Socket Server
        this._monitorPort = port ? parseInt(port, 10) + GlobalConfig.monitor.addPort : 17000;
        this._monitorInterval = (GlobalConfig.monitor.interval || 10) * 1e3;
        this.monitorServer = null;
        this.serverStats   = {};

        // Web Socket Client Instance, Send Heartbeat To Manager
        this._heartbeatHost = ManagerConfig.host;
        this._heartbeatPort = ManagerConfig.port;
        this._heartbeatInterval = ManagerConfig.heartbeatInterval * 1e3;
        this.socketClient  = null;

        this.config = {
            "maxConnections": 1000,
            "tick": 25,
            "tickTick": 50,
            "tickTickTick": 200,
        };
    }

    configure(config) {
        if (!this.isStatus("CREATED")) return;
        this.setStatus("INITIALIZING");

        if (!config || !Object.isObject(config)) config = ServerConfig;

        Object.keys(config).forEach(k => {
            this.config[k] = config[k];
        });

        Logger.debug("Load Server.config [ %j ]", this.config);
        this.setStatus("INITIALIZED");
    }

    start() {
        if (this.isStatus("CREATED")) this.configure();

        if (!this.isStatus("INITIALIZED")) return;
        this.setStatus("STARTING");

        function serverStarted() {
            // Start Main Loop
            setTimeout(this.update.bind(this), 1);

            // Server Started
            Logger.info("Server Started On [ ws://*:%s ]", this._port);

            this.regionPlugin = new RegionPlugin(this);
            this.regionPlugin.configure();
            this.regionPlugin.start();

            this.connectionPlugin = new ConnectionPlugin(this);
            this.connectionPlugin.configure();
            this.connectionPlugin.start();
            this.setStatus("STARTED");
        }
        function connectionEstablished(ws) {
            if (this.connectionPlugin.connections.length >= this.config.maxConnections) {
                ws.close();
                Logger.warn("Server Reach Max Connection Amount [ %s ], Refuse New Connect User!", this.config.maxConnections);
                return;
            }

            ws.remoteAddress = ws._socket.remoteAddress;
            ws.remotePort = ws._socket.remotePort;
            Logger.debug("Client Connect From [ %s ]", ws.remoteAddress);

            ws.packetManager = new PacketManager(this, ws);
            ws.packetManager.configure();
            ws.packetManager.start();

            ws.userManager = new UserManager(this, ws);
            ws.userManager.configure();
            ws.userManager.start();

            ws.on("message", data => {ws.packetManager.handleMessage(data);});

            function close(error) {
                // Log disconnections
                Logger.info("Client Disconnect [ %s ] with Error: ", this.socket.remoteAddress, error);

                ws.packetManager.stop();
                ws.userManager.stop();
            }

            var bindObject = {server: this, socket: ws};
            ws.on("error", close.bind(bindObject));
            ws.on("close", close.bind(bindObject));
            this.connectionPlugin.connections.push(ws);
        }
        function serverErrors(e) {
            switch (e.code) {
                case "EADDRINUSE":
                    Logger.error("Server Port [ %s ] Is In Use", this._port);
                    break;
                case "EACCES":
                    Logger.error("Please Run With Higher Auth");
                    break;
                default:
                    Logger.error("Unhandled error code: " + e.code);
            }
            process.exit(1); // Exits the program
        }
        this.socketServer = new Ws.Server({
            "port": this._port,
            "perMessageDeflate": false
        }, serverStarted.bind(this));
        this.socketServer.on("connection", connectionEstablished.bind(this));
        this.socketServer.on("error", serverErrors);

        this.startMonitor();
        this.startHeartbeat();
    }
    startMonitor() {
        // Do not start the server if the port is invalid
        this.monitorServer = new Express();
        this.monitorServer.use(bodyParser.json());
        this.monitorServer.use(bodyParser.urlencoded({ extended: false }));
        this.monitorServer.use(cookieParser());

        this.monitorServer.get("/", (req, res) => {
            Logger.info("[ %s ] Request [ %j ] With Args [ %s ]", req.method, req.headers, req.url);
            res.setHeader("Access-Control-Allow-Origin", "*");
            res.json(this.serverStats);
        });

        this.monitorServer.use(function(req, res, next) {
            var err = new Error('Not Found');
            err.status = 404;
            next(err);
        });

        this.monitorServer.use(function(err, req, res) {
            res.locals.message = err.message;
            res.locals.error = req.app.get('env') === 'development' ? err : {};

            res.status(err.status || 500);
            res.json(res.locals);
        });

        function httpServerOnListen(err) {
            Logger.info("Loaded stats server on port [ %s ]", this._monitorPort, err);
            setInterval(this.updateMonitor.bind(this), this._monitorInterval);
        }
        this.monitorServer.listen(this._monitorPort, httpServerOnListen.bind(this));
    }
    startHeartbeat() {
        this.socketClient = new Ws("ws://" + this._heartbeatHost + ":" + this._heartbeatPort);
        this.socketClient.on("open", function open() {
            setInterval(this.updateHeartbeat.bind(this), this._heartbeatInterval);
        });
        this.socketClient.on("message", function (data, flags) {
            Logger.info("Server Receive Data From Manager [ %s ] [ %j ]", data, flags);
        });
        this.socketClient.on("error", function (err) {
            Logger.error("WebSocket Connection With Manager Error Happens: ", err);
        });
    }

    update() {
        setTimeout(this.update.bind(this), 1);
        if (!this.isStatus("RUNNING")) return;

        // Calculate How Much Time Passed, Set this.time = now()
        var local = new Date();
        this.passedTicks = local - this.time;
        // No Time Passed, Usually Not Happen
        if (this.passedTicks <= 0) return;

        this.tick += this.passedTicks;
        this.tickTick += this.passedTicks;
        this.tickTickTick += this.passedTicks;
        this.time = local;

        this.connectionPlugin.update();
        this.regionPlugin.update();

        // Do tick
        if (this.tick >= this.config.tick) {
            this.tick = 0;
            this.onTick();
        }
        // Do tickTick
        if (this.tickTick >= this.config.tickTick) {
            this.tickTick = 0;
            this.onTickTick();
        }
        // Do tickTickTick
        if (this.tickTickTick >= this.config.tickTickTick) {
            this.tickTickTick = 0;
            this.onTickTickTick();
        }
    }
    onTick() {}
    onTickTick() {}
    onTickTickTick() {}
    updateMonitor() {
        this.serverStats = {
            "status": this.getStatus(),
            "time": new Date().valueOf()
        };
        if (this.isStatus("RUNNING")) {
            this.serverStats.connections = this.connectionPlugin.getConnectionsAmount();
            this.serverStats.alive = this.connectionPlugin.getAliveConnectionsAmount();
        }
    }
    updateHeartbeat() {
        if (this.socketClient) this.socketClient.sendPacket(PacketAPI.ON_HEART_BEAT, {
            "host": this._host,
            "port": this._port,
            "processId": process.pid
        });
    }

    stop() {
        if (!this.isStatus("STARTED")) return;
        this.setStatus("STOPPING");

        this.connectionPlugin.stop();
        this.regionPlugin.stop();

        this.setStatus("STOPPED");
    }
}

module.exports = Server;
export default Server;